/*
Abstract:
  CPP serializing functionality implementation

Last changed:
  $Id: output_cpp.cpp 67 2009-04-13 08:26:23Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/
#include "outputter.h"
#include "header_footer_supp.h"

#include "../io/streams.h"

#include <symbol_check.h>

#include <cassert>

#include <text.h>

namespace
{
  class SymbolOut : public std::iterator<std::output_iterator_tag, String::value_type>
  {
  public:
    SymbolOut(OutStream& str, const Output::CPPParameters& params) : Stream(str), Params(params), 
      Padding(Params.Tab, ' '), CurWidth(Params.Tab)
    {
      str << Padding;
    }

    SymbolOut& operator * ()
    {
      return *this;
    }

    void operator ++ ()
    {
    }

    void operator = (String::value_type sym)
    {
      static const Symbol::EscapePair CPP_ESC[] = {
        {'\n', 'n'}, {'\r', 'r'}, {'\t', 't'}, {'\\', '\\'}, {'\"', '\"'}, {'\'', '\''}, {'\?', '\?'}
      };
      OStringStream stream;
      stream << '\'';
      String::value_type esc(0);
      if (Symbol::Escape(sym, CPP_ESC, esc))
      {
        stream << '\\' << esc;
      }
      else if (Symbol::IsPrintable(sym))
      {
        stream << sym;
      }
      else
      {
        stream << '\\' << static_cast<unsigned>(sym) << std::dec;
      }
      stream << '\'' << ',';
      const String& strVal(stream.str());

      if (CurWidth + strVal.size() > Params.Width)
      {
        Stream << '\n' << Padding;
        CurWidth = Padding.size();
      } 
      Stream << strVal;
      CurWidth += strVal.size();

      if ('\n' == sym)
      {
        Stream << '\n' << Padding;
        CurWidth = Padding.size();
      }
    }
  private:
    OutStream& Stream;
    const Output::CPPParameters& Params;
    const String Padding;
    std::size_t CurWidth;
  };

  class CPPOut : public Output::Outputter
  {
  public:
    explicit CPPOut(const Output::CPPParameters& cppp) : Params(cppp), Padding(Params.Tab, ' ')
    {
    }

    virtual void Process(const EntriesMap& /*input*/, IO::TargetStream& /*stream*/) const
    {
      assert(!"not supported");
    }

    virtual void Process(const StringMap& input, IO::TargetStream& stream) const
    {
      Output::HeaderFooterController::Ptr hfctrl(Output::CreatePerLineController(TEXT_HEADER_FORMAT_CPP));
      OStringStream str;
      for (StringMap::const_iterator it = input.begin(), lim = input.end(); it != lim; ++it)
      {
        if (!hfctrl->ParseEntry(it->first, it->second))
        {
          OutEntry(*it, str);
        }
      }
      hfctrl->PutHeader(stream);
      stream.PutString(str.str());
      hfctrl->PutFooter(stream);
    }
  private:
    void OutEntry(const StringMap::value_type& entry, OutStream& str) const
    {
      if (Params.Inline)
      {
        str << StringFormatter::Process(TEXT_CPP_ENTRY_INLINE_BEGIN, Params.MemoryType, Params.SymbolType, entry.first);
        if (!entry.second.empty())
        {
          std::copy(entry.second.begin(), entry.second.end(), SymbolOut(str, Params));
        }
        str << '0' << TEXT_CPP_ENTRY_INLINE_END;
      }
      else
      {
        str << StringFormatter::Process(TEXT_CPP_ENTRY_NOINLINE, Params.MemoryType, Params.SymbolType, entry.first);
      }
    }
  private:
    Output::CPPParameters Params;
    const String Padding;
  };
}

namespace Output
{
  Outputter::Ptr CreateCPPOutputter(const CPPParameters& params)
  {
    return Outputter::Ptr(new CPPOut(params));
  }
}
